package cn.kgc.utils;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类 - 提供Redis常用操作封装
 *
 * @author YC
 */
@Component
public class RedisUtil {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 释放锁的Lua脚本 - 确保原子性操作
     */
    private static final String RELEASE_LOCK_LUA_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del', KEYS[1]) " +
                    "else " +
                    "return 0 " +
                    "end";

    /**
     * 设置键值并设置过期时间的Lua脚本 - 原子性操作
     */
    private static final String SET_IF_ABSENT_WITH_EXPIRE_SCRIPT =
            "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
                    "redis.call('expire', KEYS[1], ARGV[2]) " +
                    "return 1 " +
                    "else " +
                    "return 0 " +
                    "end";

    /**
     * 原子性释放分布式锁
     *
     * @param lockName 锁名称
     * @param uuid     锁标识
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockName, String uuid) {
        if (!StringUtils.hasText(lockName) || !StringUtils.hasText(uuid)) {
            return false;
        }

        Long result = redisTemplate.execute(
                new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT, Long.class),
                Collections.singletonList(lockName),
                uuid
        );
        return result != null && result == 1;
    }

    /**
     * 尝试获取分布式锁
     *
     * @param lockName   锁名称
     * @param uuid       锁标识
     * @param expireTime 过期时间
     * @param timeUnit   时间单位
     * @return 是否获取成功
     */
    public boolean tryLock(String lockName, String uuid, long expireTime, TimeUnit timeUnit) {
        if (!StringUtils.hasText(lockName) || !StringUtils.hasText(uuid) || expireTime <= 0) {
            return false;
        }

        return Boolean.TRUE.equals(
                redisTemplate.opsForValue().setIfAbsent(
                        lockName,
                        uuid,
                        expireTime,
                        timeUnit
                )
        );
    }

    /**
     * 值自增1
     *
     * @param key 键
     * @return 自增后的值，如果键不存在会先初始化为0再执行INCR操作
     */
    public long increment(String key) {
        if (!StringUtils.hasText(key)) {
            throw new IllegalArgumentException("Key cannot be null or empty");
        }
        return redisTemplate.opsForValue().increment(key);
    }

    /**
     * 值自增指定步长
     *
     * @param key   键
     * @param delta 步长
     * @return 自增后的值
     */
    public long increment(String key, long delta) {
        if (!StringUtils.hasText(key) || delta < 0) {
            throw new IllegalArgumentException("Invalid parameters");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 值自增1，仅在第一次自增时设置过期时间
     *
     * @param key      键
     * @param time     过期时间
     * @param timeUnit 时间单位
     * @return 自增后的值
     */
    public long incrementFirstExpire(String key, long time, TimeUnit timeUnit) {
        if (!StringUtils.hasText(key) || time <= 0) {
            throw new IllegalArgumentException("Invalid parameters");
        }

        Long value = redisTemplate.opsForValue().increment(key);
        if (value != null && value == 1) {
            redisTemplate.expire(key, time, timeUnit);
        }
        return value != null ? value : 0;
    }

    /**
     * 值自增1，每次自增都重新设置过期时间
     *
     * @param key      键
     * @param time     过期时间
     * @param timeUnit 时间单位
     * @return 自增后的值
     */
    public long incrementAlwaysExpire(String key, long time, TimeUnit timeUnit) {
        if (!StringUtils.hasText(key) || time <= 0) {
            throw new IllegalArgumentException("Invalid parameters");
        }

        Long value = redisTemplate.opsForValue().increment(key);
        if (value != null) {
            redisTemplate.expire(key, time, timeUnit);
        }
        return value != null ? value : 0;
    }

    /**
     * 值自减1
     *
     * @param key 键
     * @return 自减后的值
     */
    public long decrement(String key) {
        if (!StringUtils.hasText(key)) {
            throw new IllegalArgumentException("Key cannot be null or empty");
        }
        return redisTemplate.opsForValue().decrement(key);
    }

    /**
     * 值自减指定步长
     *
     * @param key   键
     * @param delta 步长
     * @return 自减后的值
     */
    public long decrement(String key, long delta) {
        if (!StringUtils.hasText(key) || delta < 0) {
            throw new IllegalArgumentException("Invalid parameters");
        }
        return redisTemplate.opsForValue().decrement(key, delta);
    }

    /**
     * 如果key不存在则设置值（带过期时间）
     *
     * @param key      键
     * @param value    值
     * @param time     过期时间
     * @param timeUnit 时间单位
     * @return 是否设置成功
     */
    public boolean setIfAbsent(String key, Object value, long time, TimeUnit timeUnit) {
        if (!StringUtils.hasText(key) || time <= 0) {
            return false;
        }
        return Boolean.TRUE.equals(
                redisTemplate.opsForValue().setIfAbsent(key, value, time, timeUnit)
        );
    }

    /**
     * 设置键值对
     *
     * @param key   键
     * @param value 值
     */
    public void set(String key, Object value) {
        if (!StringUtils.hasText(key)) {
            throw new IllegalArgumentException("Key cannot be null or empty");
        }
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 设置键值对（带过期时间）
     *
     * @param key      键
     * @param value    值
     * @param time     过期时间
     * @param timeUnit 时间单位
     */
    public void set(String key, Object value, long time, TimeUnit timeUnit) {
        if (!StringUtils.hasText(key) || time <= 0) {
            throw new IllegalArgumentException("Invalid parameters");
        }
        redisTemplate.opsForValue().set(key, value, time, timeUnit);
    }

    /**
     * 获取值
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        if (!StringUtils.hasText(key)) {
            return null;
        }
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 获取指定类型的值
     *
     * @param key   键
     * @param clazz 值类型
     * @param <T>   类型泛型
     * @return 值
     */
    public <T> T get(String key, Class<T> clazz) {
        Object value = get(key);
        return clazz.isInstance(value) ? clazz.cast(value) : null;
    }

    /**
     * 获取Hash中的字段值
     *
     * @param key   键
     * @param field 字段名
     * @return 字段值
     */
    public Object hGet(String key, Object field) {
        if (!StringUtils.hasText(key) || field == null) {
            return null;
        }
        return redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 判断键是否存在
     *
     * @param key 键
     * @return 是否存在
     */
    public boolean hasKey(String key) {
        if (!StringUtils.hasText(key)) {
            return false;
        }
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    /**
     * 判断Hash中字段是否存在
     *
     * @param key   键
     * @param field 字段名
     * @return 是否存在
     */
    public boolean hHasKey(String key, Object field) {
        if (!StringUtils.hasText(key) || field == null) {
            return false;
        }
        return redisTemplate.opsForHash().hasKey(key, field);
    }

    /**
     * 设置Hash字段值
     *
     * @param key   键
     * @param field 字段名
     * @param value 字段值
     */
    public void hSet(String key, Object field, Object value) {
        if (!StringUtils.hasText(key) || field == null) {
            return;
        }
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 批量设置Hash字段值
     *
     * @param key 键
     * @param map 字段-值映射
     */
    public void hSetAll(String key, Map<Object, Object> map) {
        if (!StringUtils.hasText(key) || CollectionUtils.isEmpty(map)) {
            return;
        }
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 获取整个Hash
     *
     * @param key 键
     * @return 字段-值映射
     */
    public Map<Object, Object> hGetAll(String key) {
        if (!StringUtils.hasText(key)) {
            return Collections.emptyMap();
        }
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 删除Hash中的字段
     *
     * @param key    键
     * @param fields 字段名
     * @return 删除的字段数量
     */
    public long hDelete(String key, Object... fields) {
        if (!StringUtils.hasText(key) || fields == null || fields.length == 0) {
            return 0;
        }
        return redisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 删除键
     *
     * @param key 键
     * @return 是否删除成功
     */
    public boolean delete(String key) {
        if (!StringUtils.hasText(key)) {
            return false;
        }
        return Boolean.TRUE.equals(redisTemplate.delete(key));
    }

    /**
     * 删除key中对应的filedName
     *
     * @param key       键
     * @param fieldName 键
     * @return 是否删除成功
     */
    public boolean delete(String key, String fieldName) {
        return redisTemplate.opsForHash().delete(key, fieldName) > 0;
    }

    /**
     * 批量删除键
     *
     * @param keys 键集合
     * @return 成功删除的数量
     */
    public long delete(Collection<String> keys) {
        if (CollectionUtils.isEmpty(keys)) {
            return 0;
        }
        Long count = redisTemplate.delete(keys);
        return count != null ? count : 0;
    }

    /**
     * 设置过期时间
     *
     * @param key      键
     * @param time     时间
     * @param timeUnit 时间单位
     * @return 是否设置成功
     */
    public boolean expire(String key, long time, TimeUnit timeUnit) {
        if (!StringUtils.hasText(key) || time <= 0) {
            return false;
        }
        return Boolean.TRUE.equals(redisTemplate.expire(key, time, timeUnit));
    }

    /**
     * 获取过期时间
     *
     * @param key      键
     * @param timeUnit 时间单位
     * @return 剩余过期时间
     */
    public Long getExpire(String key, TimeUnit timeUnit) {
        if (!StringUtils.hasText(key)) {
            return null;
        }
        return redisTemplate.getExpire(key, timeUnit);
    }

    /**
     * Hash字段值自增
     *
     * @param key   键
     * @param field 字段名
     * @param delta 步长
     * @return 自增后的值
     */
    public long hIncrement(String key, Object field, long delta) {
        if (!StringUtils.hasText(key) || field == null) {
            return 0;
        }
        return redisTemplate.opsForHash().increment(key, field, delta);
    }

    /**
     * 添加元素到Set
     *
     * @param key    键
     * @param values 值
     * @return 添加成功的数量
     */
    public long sAdd(String key, Object... values) {
        if (!StringUtils.hasText(key) || values == null || values.length == 0) {
            return 0;
        }
        Long count = redisTemplate.opsForSet().add(key, values);
        return count != null ? count : 0;
    }

    /**
     * 从Set中移除元素
     *
     * @param key    键
     * @param values 值
     * @return 移除成功的数量
     */
    public long sRemove(String key, Object... values) {
        if (!StringUtils.hasText(key) || values == null || values.length == 0) {
            return 0;
        }
        Long count = redisTemplate.opsForSet().remove(key, values);
        return count != null ? count : 0;
    }

    /**
     * 获取Set中的所有元素
     *
     * @param key 键
     * @return 元素集合
     */
    public Set<Object> sMembers(String key) {
        if (!StringUtils.hasText(key)) {
            return Collections.emptySet();
        }
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 添加元素到List尾部
     *
     * @param key   键
     * @param value 值
     * @return 操作后列表的长度
     */
    public long lPush(String key, Object value) {
        if (!StringUtils.hasText(key) || value == null) {
            return 0;
        }
        Long length = redisTemplate.opsForList().rightPush(key, value);
        return length != null ? length : 0;
    }

    /**
     * 从List头部弹出元素
     *
     * @param key 键
     * @return 弹出的元素
     */
    public Object lPop(String key) {
        if (!StringUtils.hasText(key)) {
            return null;
        }
        return redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 获取List范围元素
     *
     * @param key   键
     * @param start 起始索引
     * @param end   结束索引
     * @return 元素列表
     */
    public List<Object> lRange(String key, long start, long end) {
        if (!StringUtils.hasText(key)) {
            return Collections.emptyList();
        }
        return redisTemplate.opsForList().range(key, start, end);
    }
}